//
// Created by Apple on 2021/5/16.
//

#include "WebsocketClient.h"

// Start the asynchronous operation
void WebsocketClient::run( char const* host, char const* port, char const* text)
{
    // Save these for later
    host_ = host;
    text_ = text;

    // Look up the domain name
    resolver_.async_resolve(
            host,
            port,
            beast::bind_front_handler(
                    &WebsocketClient::on_resolve,
                    shared_from_this()));
}

void WebsocketClient::on_resolve( beast::error_code ec, tcp::resolver::results_type results)
{
    if(ec)
        return fail(ec, "resolve");

    // Set the timeout for the operation
    beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));

    // Make the connection on the IP address we get from a lookup
    beast::get_lowest_layer(ws_).async_connect(
            results,
            beast::bind_front_handler(
                    &WebsocketClient::on_connect,
                    shared_from_this()));
}

void WebsocketClient:: on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type ep)
{
    if(ec)
        return fail(ec, "connect");

    // Turn off the timeout on the tcp_stream, because
    // the websocket stream has its own timeout system.
    beast::get_lowest_layer(ws_).expires_never();

    // Set suggested timeout settings for the websocket
    ws_.set_option(
            websocket::stream_base::timeout::suggested(
                    beast::role_type::client));

    // Set a decorator to change the User-Agent of the handshake
    ws_.set_option(websocket::stream_base::decorator(
            [](websocket::request_type& req)
            {
                req.set(http::field::user_agent,
                        std::string(BOOST_BEAST_VERSION_STRING) +
                        " websocket-client-async");
            }));

    // Update the host_ string. This will provide the value of the
    // Host HTTP header during the WebSocket handshake.
    // See https://tools.ietf.org/html/rfc7230#section-5.4
    host_ += ':' + std::to_string(ep.port());

    // Perform the websocket handshake
    ws_.async_handshake(host_, "/",
                        beast::bind_front_handler(
                                &WebsocketClient::on_handshake,
                                shared_from_this()));
}

void WebsocketClient::on_handshake(beast::error_code ec)
{
    if(ec)
        return fail(ec, "handshake");

    // Send the message
    ws_.async_write(
            net::buffer(text_),
            beast::bind_front_handler(
                    &WebsocketClient::on_write,
                    shared_from_this()));
}

void WebsocketClient::on_write(beast::error_code ec, std::size_t bytes_transferred)
{
    boost::ignore_unused(bytes_transferred);

    if(ec)
        return fail(ec, "write");

    // Read a message into our buffer
    ws_.async_read(
            buffer_,
            beast::bind_front_handler(
                    &WebsocketClient::on_read,
                    shared_from_this()));
}

void WebsocketClient::on_read(beast::error_code ec,std::size_t bytes_transferred)
{
    boost::ignore_unused(bytes_transferred);

    if(ec)
        return fail(ec, "read");

    // Close the WebSocket connection
    ws_.async_close(websocket::close_code::normal,
                    beast::bind_front_handler(
                            &WebsocketClient::on_close,
                            shared_from_this()));
}

void WebsocketClient::on_close(beast::error_code ec)
{
    if(ec)
        return fail(ec, "close");

    // If we get here then the connection is closed gracefully

    // The make_printable() function helps print a ConstBufferSequence
    std::cout << beast::make_printable(buffer_.data()) << std::endl;
}

void WebsocketClient::fail(beast::error_code ec, char const* what)
{
    std::cerr << what << ": " << ec.message() << "\n";
}
